package gitee.derumin.lib_for_derumin.helpers.config;

import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import gitee.derumin.lib_for_derumin.helpers.config.configserializers.*;
import gitee.derumin.lib_for_derumin.logger.DeruminLogger;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.loader.api.FabricLoader;
import org.apache.commons.io.FileUtils;
import org.jetbrains.annotations.Nullable;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public abstract class ConfigHelper {
    private static final DeruminLogger LOGGER = DeruminLogger.getLogger();
    private static final Map<String, ConfigController> CONTROLLERS = new HashMap<>();
    public static final Path CONFIG_PATH = FabricLoader.getInstance().getConfigDir();

    static {
        if (!CONFIG_PATH.toFile().exists() && !CONFIG_PATH.toFile().mkdirs()){
            LOGGER.error("[Derumin Config Helper]Can't find a Config directory! No configs would be reloaded or saved!", DeruminLogger.AnnounceType.OPS);
        }
        addAvailableSerializer(new BlockSerializer());
        addAvailableSerializer(new BooleanSerializer());
        addAvailableSerializer(new ByteSerializer());
        addAvailableSerializer(new CharacterSerializer());
        addAvailableSerializer(new DoubleSerializer());
        addAvailableSerializer(new EntityTypeSerializer());
        addAvailableSerializer(new FloatSerializer());
        addAvailableSerializer(new IntegerSerializer());
        addAvailableSerializer(new ItemSerializer());
        addAvailableSerializer(new LongSerializer());
        addAvailableSerializer(new PathSerializer());
        addAvailableSerializer(new ShortSerializer());
        addAvailableSerializer(new StringSerializer());
    }

    public static void init(){
        ServerLifecycleEvents.SERVER_STOPPING.register(server -> saveAll());
    }

    /**
     * 注册用于处理配置对象的字段的分析器ConfigFieldSerializer。
     * 必须在注册配置对象之前进行注册。
     * @param serializer 用于分析字段的分析器的对象。
     */
    public static void addAvailableSerializer(ConfigFieldSerializer<?> serializer){
        ConfigManager.addAvailableSerializer(serializer);
    }

    /**
     * 将配置对象列入自动控制列表中，对象的所有public字段将被自动控制。若需手动控制，请使对象实现ConfigController接口。
     * 配置对象在传入时的各字段的值将被当做是默认值。在字段被更改后，可以使用reset方法重置到默认值。
     * 自动控制支持所有package gitee.derumin.lib_for_derumin.helpers.config.configserializers包内支持的“基本”类型，以及其Array、ArrayList、
     * HashMap的嵌套组合；同一嵌套层内的Array应当具有相同的长度；所有的Collection将被当做ArrayList处理；所有的Map将被当做HashMap处理。
     * 如果配置对象中含有已有的serializer支持的类以外的字段，需要将处理该字段的ConfigFieldSerializer对象使用addAvailableSerializer进行注册，
     * ConfigFieldSerializer的注册应当在配置对象的注册之前。
     * 请勿使用相同的注册名重复注册，否则会导致服务器强制关闭。
     * @param config     将被自动控制的配置对象。只有传入的对象会被自动控制。
     * @param configName 配置的注册名，可以留空。留空时使用配置的类名进行注册。
     */
    public static void register(Object config, @Nullable String configName){
        if (configName == null){
            configName = config.getClass().getName();
        }
        if (contains(configName)){
            LOGGER.fatal("The config name '" + configName + "' is conflict with an existing config!");
        }
        if (config instanceof ConfigController controller){
            controller.initializeFromJson(getConfigFileByName(configName));
            CONTROLLERS.put(configName, controller);
        }else {
            ConfigManager manager = new ConfigManager(config, configName);
            manager.initializeFromJson(getConfigFileByName(configName));
            CONTROLLERS.put(configName, manager);
        }
    }

    /**
     * 返回持有的所有配置的注册名列表。
     * @return 配置的注册名的列表。
     */
    public static List<String> getConfigNames(){
        return List.copyOf(CONTROLLERS.keySet());
    }

    /**
     * 使用配置的注册名将目标配置保存到配置文件。
     * @param configName 目标配置的注册名。
     * @return 若目标配置存在，则返回true，否则返回false。注意与保存是否成功无关。
     */
    public static boolean saveConfigByName(String configName){
        if (CONTROLLERS.containsKey(configName)) {
            save(configName, CONTROLLERS.get(configName));
            return true;
        }else {
            declareConfigNotFound(configName, DeruminLogger.AnnounceType.OPS);
            return false;
        }
    }

    /**
     * 尝试将所有已注册的配置保存到配置文件。
     */
    public static void saveAll(){
        CONTROLLERS.forEach(ConfigHelper::save);
    }

    /**
     * 使用配置的注册名重新从配置文件读取目标配置。
     * @param configName 目标配置的注册名。
     * @return 若目标配置存在，则返回true，否则返回false。注意与读取是否成功无关。
     */
    public static boolean reloadConfigByName(String configName){
        if (CONTROLLERS.containsKey(configName)){
            JsonObject object = getConfigFileByName(configName);
            if (object != null){
                CONTROLLERS.get(configName).initializeFromJson(object);
                return true;
            }else {
                return false;
            }
        }else {
            declareConfigNotFound(configName, DeruminLogger.AnnounceType.OPS);
            return false;
        }
    }

    /**
     * 使用配置的注册名重置目标配置的所有字段。
     * @param configName 目标配置的注册名。
     * @return 若目标配置存在，则返回true，否则返回false。注意与重置是否成功无关。
     */
    public static boolean resetConfigByName(String configName){
        if (CONTROLLERS.containsKey(configName)){
            ConfigController controller = CONTROLLERS.get(configName);
            controller.initializeFromJson(controller.getDefaultJson());
            return true;
        }else {
            declareConfigNotFound(configName, DeruminLogger.AnnounceType.OPS);
            return false;
        }
    }

    public static boolean modifyConfigByName(String name, JsonObject input){
        if (!contains(name)){
            declareConfigNotFound(name, DeruminLogger.AnnounceType.NONE);
            return false;
        }
        if (CONTROLLERS.get(name).modifyFrom(input)){
            LOGGER.info("[Derumin Config Helper]Changed a config entry in: " + name + ".", DeruminLogger.AnnounceType.OPS);
            return true;
        }else {
            LOGGER.info("[Derumin Config Helper]Failed to change config entry in : " + name + ".", DeruminLogger.AnnounceType.NONE);
            return false;
        }
    }

    /**
     * 使用配置的注册名查询自动控制列表中是否有对应配置。
     * @param configName 目标配置的注册名。
     * @return true:存在；false:不存在。
     */
    public static boolean contains(String configName){
        return CONTROLLERS.containsKey(configName);
    }

    /**
     * 使用配置的注册名获取目标配置。
     * @param configName 目标配置的注册名。
     * @return 若查询到目标配置，则返回之，否则返回null。
     */
    public static JsonObject getConfigByName(String configName, boolean record){
        if (CONTROLLERS.containsKey(configName)){
            return CONTROLLERS.get(configName).writeConfigToJson();
        }else {
            if (record) {
                declareConfigNotFound(configName, DeruminLogger.AnnounceType.NONE);
            }
            return null;
        }
    }

    public static JsonObject getDefaultConfigJson(String configName){
        if (CONTROLLERS.containsKey(configName)){
            return CONTROLLERS.get(configName).getDefaultJson();
        }else {
            declareConfigNotFound(configName, DeruminLogger.AnnounceType.OPS);
            return null;
        }
    }

    /**
     * 使用配置的注册名删除目标配置的配置文件。
     * @param configName 目标配置的注册名。
     * @return 若目标配置文件存在且成功删除，则返回true，否则返回false。
     */
    public static boolean deleteConfigFileByName(String configName){
        File file = CONFIG_PATH.resolve(configName + ".json").toFile();
        if (!file.exists()){
            LOGGER.info("[Derumin Config Helper]Target file: " + configName + ".json is not found!", DeruminLogger.AnnounceType.OPS);
            return false;
        }else if (!file.delete()){
            LOGGER.error("[Derumin Config Helper]Failed to delete the config file for: " + configName + "!", DeruminLogger.AnnounceType.OPS);
            return false;
        }else {
            LOGGER.info("[Derumin Config Helper]Target file: " + configName + ".json is deleted.", DeruminLogger.AnnounceType.OPS);
            return true;
        }
    }

    /**
     * 使用配置的注册名将目标配置的配置文件读取为JsonObject。
     * @param configName 目标配置的注册名。
     * @return 若读取到目标配置文件，则返回之，否则返回null。
     */
    public static @Nullable JsonObject getConfigFileByName(String configName){
        File file1 = CONFIG_PATH.resolve(configName + ".json").toFile();
        JsonObject jsonObject = null;
        if (file1.exists()){
            try {
                jsonObject = JsonParser.parseString(FileUtils.readFileToString(file1, StandardCharsets.UTF_8)).getAsJsonObject();
            } catch (IOException e) {
                LOGGER.error("[Derumin Config Helper]Failed to read config file for: " + configName + "!", DeruminLogger.AnnounceType.OPS);
                return null;
            }
        }
        if (jsonObject == null){
            LOGGER.warn("[Derumin Config Helper]Can't find any configs files for: " + configName + "!", DeruminLogger.AnnounceType.OPS);
        }
        return jsonObject;
    }

    /**
     * 将Json文本写入文件。
     * @param fileName 将被保存的文件名。
     * @param jsonObject 将被写入的Json文本。
     */
    private static void writeJsonToFile(String fileName, JsonObject jsonObject){
        File file = CONFIG_PATH.resolve(fileName + ".json").toFile();
        if (file.exists() && !file.delete()){
            LOGGER.error("[Derumin Config Helper]Failed to recover config file for: " + fileName + "!", DeruminLogger.AnnounceType.OPS);
            return;
        }
        try {
            if (file.createNewFile()){
                try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter( file, false))) {
                    bufferedWriter.write(new GsonBuilder().setPrettyPrinting().create().toJson(jsonObject));
                }
            }
        } catch (IOException e) {
            LOGGER.error("[Derumin Config Helper]Failed to write config file for: " + fileName + "!", DeruminLogger.AnnounceType.OPS);
        }
    }

    private static void save(String configName, ConfigController controller){
        new Thread(() -> writeJsonToFile(configName, controller.writeConfigToJson())).start();
    }

    private static void declareConfigNotFound(String configName, DeruminLogger.AnnounceType type){
        LOGGER.warn("[Derumin Config Helper]Cant find the appointed config '"+ configName + "'!", type);
    }

}
